
#pragma once

#include <tbb/tick_count.h>
#include <tbb/parallel_reduce.h>
#include <tbb/blocked_range.h>
#include <tbb/task_scheduler_init.h>

#include "Common.h"

//Functor for parallel_reduce.
template <class T>
struct tbbReduceBody
{
	private:
		Matrix2x2<T> sum;
		int splitted;		//flag to make one less operation for splitted bodies
	public:
		//Constructor fills sum with initial matrix.
		inline tbbReduceBody()
		{
			sum =  Matrix2x2<T>(1, 1, 1, 0);
			splitted = 0;
		}
		//Splitting constructor.
		inline tbbReduceBody(tbbReduceBody<T> &other,  tbb::split)
		{
			sum = Matrix2x2<T>(1, 1, 1, 0);
			splitted = 1;
		}
		//Returns required fibonacci number.
		inline const T &getFibonacciNumber() const
		{ return sum(0, 0); }
		//Join point
		inline void join(tbbReduceBody<T> &s)
		{ sum = sum * s.sum; }
		//Processes multiplications
		inline void operator()(const tbb::blocked_range<int> &r)
		{
			for(int k = r.begin() + splitted; k < r.end(); k++)
				sum = sum * Matrix2x2<T>(1, 1, 1, 0);
			splitted = 0;		//resetting.
		}
};

//Calculates n-th fibonacci number using tbb::parallel_reduce.
//Returns required number + elapsed time (in seconds).
template <class WorkType>
std::pair<WorkType, double> tbbFibonacci_Reduce(size_t n)
{
    tbbReduceBody<WorkType> b;
	tbb::tick_count t_start = tbb::tick_count::now();
	parallel_reduce(tbb::blocked_range<int>(1, (int)n, 3), b);
	tbb::tick_count t_end = tbb::tick_count::now();
	return std::make_pair(b.getFibonacciNumber(), (t_end - t_start).seconds());
}
